/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file linked_list_fast.cpp
 * @author TooOld2Rock'nRoll
 * @date dec/2004
 * @see linked_list_fast.hpp
 */

/*---- Includes ----*/
#include "toolbox/linked_list_fast.hpp"
#include "toolbox/debug.h"

#ifdef UNIT_TESTING
#include <CppUTest/TestHarness.h>
#endif


/*---- Methods Definitions ----*/
/**
 * @brief Start a new empty List.
 */
template <typename T>
LinkedList<T>::LinkedList ()
{
    _debug (TRACE, "%s ()", __FUNCTION__);
}//End Construct


/**
 * @brief Delete all nodes and free all data in the List.
 *
 * Same as clean(), but prints a benchmark report at the end when
 *   compiled with ENABLE_BENCHMARK flag.
 */
template <typename T>
LinkedList<T>::~LinkedList ()
{
    _debug (TRACE, "%s ()", __FUNCTION__);

    this->clean (true);
}//End Destructor


/**
 * @brief Push a new package to the head of the List.
 *
 * @warning The data on the new package will <b>NOT</b> be copied, only the reference saved!
 *
 * @param new_package - data pointer to be saved.
 *
 * @throws std::invalid_argument - if the new package is null (it makes no sense!).
 */
template <typename T>
void LinkedList<T>::insert (T *new_package)
{
    //New node to put in this
    LinkedList::node_s *new_node = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!new_package)
        throw std::invalid_argument (fmt::format ("{0}:{1} - new package is NULL!", __LINE__, __FILE__));

    //Save information passed to function
    new_node = new LinkedList::node_s ();
    new_node->_package = new_package;

    new_node->_next = this->_head; //move list head
    this->_head = new_node; //new list head

    ++this->_size; //Increase list Size;
}//End insert ()


/**
 * @brief Removes the package at the list head.
 * @remark Client is responsible for freeing the returned pointer, we're only storing it!
 *
 * @return Pointer to the package at the list head, null if the list is empty.
 */
template <typename T>
T* LinkedList<T>::remove ()
{
    LinkedList::node_s *aux = nullptr;
    T *save = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_head)
        return nullptr;

    //save node package and free node
    aux = this->_head;
    save = this->_head->_package;

    //reposition head
    this->_head = this->_head->_next;
    delete (aux);
    --this->_size;

    return save;
}//End remove ()


/**
 * @brief Removes the package under the read head from the list and increment the reading head's position.
 * @remark Client is responsible for freeing the returned pointer, we're only storing it!
 *
 * @return Pointer to the package under read head, null if at the end of the list or if list is empty.
 */
template <typename T>
T* LinkedList<T>::remove (iterator &it)
{
    T *save = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!it._readhead)
    {
        _debug (INFO, "Iterator is empty, nothing to remove!");
        return nullptr;
    }//end else if (!this->_readhead)

    //save node package and free node
    save = it._readhead->_package;
    if (it._aux_node)
        it._aux_node->_next = it._readhead->_next;
    else //read head under first node!
        this->_head = it._readhead->_next;

    delete (it._readhead);
    --this->_size;

    //reposition readhead
    it._readhead = (it._aux_node ? it._aux_node->_next : this->_head);

    return save;
}//End remove ()


/**
 * @brief Reads the package under the read head.
 *
 * @return Pointer to the package under the read head, null if at the end of the list or if list is empty.
 */
template <typename T>
T* LinkedList<T>::iterator::read () const
{
    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_readhead)
        return nullptr;

    return this->_readhead->_package;
}//End read ()


/**
 * @brief Increments the read head position to the next node.
 *
 * @return Pointer to the next package on the list, null if at the end of the list or if list is empty.
 */
template <typename T>
T* LinkedList<T>::iterator::next ()
{
    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_readhead)
        return nullptr;

    this->_aux_node = this->_readhead;
    this->_readhead = this->_readhead->_next;

    return (this->_readhead ? this->_readhead->_package : nullptr);
}//End next ()


/**
 * @brief Swaps the package under the read head for a new one.
 * @remark Client is responsible for freeing the returned package pointer!
 *
 * @param new_package - data pointer to be saved in the list in place of the current one.
 *
 * @return Pointer to the old package under read head, null if at the end of the list or if list is empty.
 *
 * @throws std::invalid_argument - if the new package is null (it makes no sense!).
 * @throws std::logic_error - if the iterator is in the wrong state.
 */
template <typename T>
T* LinkedList<T>::iterator::swap (T *new_package)
{
    T *old_package = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!new_package)
        throw std::invalid_argument (fmt::format ("{0}:{1} - new package is null!", __LINE__, __FILE__));
    else if (!this->_readhead)
        throw std::logic_error (fmt::format ("{0}:{1} - iterator is empty, nothing to swap with!", __LINE__, __FILE__));

    old_package = this->_readhead->_package;
    this->_readhead->_package = new_package;

    return old_package;
}//End swap ()


/**
 * @brief Search for a package in the list.
 *
 * Search will begin from the provided iterator and traverse the list comparing its nodes with the desired key.<br>
 * If a match is found, the iterator will be pointing to the desired node and to find more matches, simple feed the
 *   iterator the method as many times as required, until the end of the list is reached.
 *
 * @param begin - reference to a iterator pointing to the beginning of the search.
 * @param key - reference to the desired data to be found.
 *
 * @return Pointer to iterator pointing to the desired node or nullptr if no match is found.
 */
template <typename T>
typename LinkedList<T>::iterator* LinkedList<T>::search (LinkedList<T>::iterator &begin, T &key)
{
    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!begin._readhead)
        return nullptr;

    do {
        if (key == *begin._readhead->_package)
            break;
    } while (begin.next ());

    return (begin._readhead ? &begin : nullptr);
}//End search ()


/**
 * @brief Remove all nodes from the list.
 *
 * @param del_packages - if the method should deallocate the package pointers for the user or not.
 */
template <typename T>
void LinkedList<T>::clean (bool del_packages)
{
    LinkedList::node_s *aux_node = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_head)
        return;

    do {
        //remove the first node from the list
        aux_node = this->_head;
        this->_head = this->_head->_next;

        //free the resources
        if (del_packages)
            delete (aux_node->_package);
        delete (aux_node);

        --this->_size;
    } while (this->_head);
}//End clean ()


#ifdef UNIT_TESTING
static int *x = nullptr,
           *buff = nullptr,
           cb_count = 0;
static LinkedList<int> *myList = nullptr;

TEST_GROUP(linkedlist_f_cpp)
{
    void setup ()
    {
        myList = new LinkedList<int> ();
    }//end setup ()

    void teardown ()
    {
        delete (myList);
        myList = nullptr;

        x = nullptr;
        buff = nullptr;
        cb_count = 0;
    }//end teardown ()
};//end TEST_GROUP(linkedlist_f_cpp)


TEST(linkedlist_f_cpp, check_empty_read)
{
    CHECK(myList->begin().read () == nullptr);
}

TEST(linkedlist_f_cpp, insert_and_check_read)
{
    x = new int (rand ());
    myList->insert (x);

    CHECK_EQUAL(*x, *((int *)myList->begin().read ()));
}

TEST(linkedlist_f_cpp, insert_two_check_head)
{
    x = new int (rand ());
    myList->insert (x);

    x = new int (rand ());
    myList->insert (x);

    CHECK_EQUAL(*x, *((int *)myList->begin().read ()));
}//end TEST(linkedlist_f_cpp, insert_two_reset_and_check_read)

TEST(linkedlist_f_cpp, add_N_traverse_the_list_with_next)
{
    int i = 0;
    LinkedList<int>::iterator it;

    for (i=10; i--; )
        myList->insert (new int (i));

    it = myList->begin();
    do {
        ++i;
        CHECK_EQUAL(i, *it.read ());
    } while (it.next ());
}//end TEST(linkedlist_f_cpp, insert_two_and_next_twice)

TEST(linkedlist_f_cpp, add_N_traverse_the_list_with_incremental_for)
{
    int i = 0;

    for (i=10; i--; )
        myList->insert (new int (i));

    //i should be -1 and list should be ordered 0->9
    for(auto it=myList->begin(); it!=myList->end(); ++it)
    {
        ++i;
        CHECK_EQUAL(i, *it.read ());
    }//end for (auto it : myList->begin ())
}//end TEST(linkedlist_f_cpp, add_N_traverse_the_list_with_incremental_for)

TEST(linkedlist_f_cpp, add_N_traverse_the_list_with_for_each)
{
    int i = 0;

    for (i=10; i--; )
        myList->insert (new int (i));

    //i should be -1 and list should be ordered 0->9
    for(auto it : *myList)
    {
        ++i;
        CHECK_EQUAL(i, *it.read ());
    }//end for (auto it : myList->begin ())
}//end TEST(linkedlist_f_cpp, add_N_traverse_the_list_with_for_each)

TEST(linkedlist_f_cpp, insert_N_and_check_search)
{
    int i = 0;
    LinkedList<int>::iterator it;

    for (i=10; i--; )
        myList->insert (new int (i));

    i = 5;
    it = myList->begin ();

    CHECK(myList->search (it, i) != nullptr);
    CHECK_EQUAL(i, *it.read ());
}//end TEST(linkedlist_f_cpp, insert_N_and_check_search)

TEST(linkedlist_f_cpp, remove_on_empty_list)
{
    CHECK(myList->remove () == nullptr);
}

TEST(linkedlist_f_cpp, insert_and_remove)
{
    x = new int (rand ());
    myList->insert (x);
    buff = myList->remove ();

    CHECK_EQUAL(*x, *buff);

    delete (buff);
}

TEST(linkedlist_f_cpp, insert_once_reset_remove_twice_check_NULL)
{
    x = new int (rand ());
    myList->insert (x);

    buff = myList->remove ();
    CHECK(buff != nullptr);
    delete (buff);
    buff = nullptr;

    CHECK(myList->remove () == nullptr);
}

TEST(linkedlist_f_cpp, insert_twice_reset_remove_twice_check_order)
{
    int *first = new int (rand ());
    myList->insert (first);

    x = new int (rand ());
    myList->insert (x);

    buff = myList->remove ();
    CHECK(buff != nullptr);
    CHECK_EQUAL(*x, *buff);
    delete (buff);
    buff = nullptr;

    buff = myList->remove ();
    CHECK(buff != nullptr);
    CHECK_EQUAL(*first, *buff);
    delete (buff);
    buff = nullptr;
}//end TEST(linkedlist_f_cpp, insert_twice_reset_remove_twice_check_order)

TEST(linkedlist_f_cpp, insert_twice_remove_once_check_read)
{
    int *first = new int (rand ());
    myList->insert (first);

    x = new int (rand ());
    myList->insert (x);

    buff = myList->remove ();
    delete (buff);

    CHECK_EQUAL(*first, *myList->begin().read ());
}//end TEST(linkedlist_f_cpp, insert_twice_pop_once_check_first)

TEST(linkedlist_f_cpp, insert_N_and_check_clean)
{
    int i = 0;

    for (i=10; i--; )
        myList->insert (new int (rand ()));

    myList->clean (true);

    CHECK(myList->begin().read () == nullptr);
    UNSIGNED_LONGS_EQUAL(myList->size (), 0);
}//end TEST(linkedlist_f_cpp, insert_N_and_check_clean)

TEST(linkedlist_f_cpp, check_empty_true)
{
    CHECK(myList->empty ());
}//end TEST(linkedlist_f_cpp, check_empty_true)

TEST(linkedlist_f_cpp, check_empty_false)
{
    myList->insert (new int (rand ()));

    CHECK_FALSE(myList->empty ());
}//end TEST(linkedlist_f_cpp, check_empty_false)

TEST(linkedlist_f_cpp, insert_N_check_size)
{
    int i = 0;

    for (i=10; i--; )
        myList->insert (new int (rand ()));

    UNSIGNED_LONGS_EQUAL(myList->size (), 10);
}//end TEST(linkedlist_f_cpp, insert_check_size)

TEST(linkedlist_f_cpp, insert_N_remove_check_size)
{
    int i = 0;

    for (i=10; i--; )
        myList->insert (new int (rand ()));

    for (i=8; i--; )
        delete (myList->remove ());

    UNSIGNED_LONGS_EQUAL(myList->size (), 2);
}//end TEST(linkedlist_f_cpp, insert_N_remove_check_size)

TEST(linkedlist_f_cpp, insert_N_clean_insert_N_sanity_check)
{
    int i = 0;

    for (i=10; i--; )
        myList->insert (new int (rand ()));

    myList->clean (true);
    CHECK(myList->empty ());

    for (i=10; i--; )
    {
        x = new int (rand ());
        myList->insert (x);
    }//end for ( ; i--; )
    UNSIGNED_LONGS_EQUAL(myList->size (), 10);

    CHECK_EQUAL(*x, *myList->begin().read ());
}//end TEST(linkedlist_f_cpp, insert_N_remove_check_size)

TEST(linkedlist_f_cpp, insert_N_search_swap_and_read)
{
    int i = 0;
    LinkedList<int>::iterator it;

    for (i=10; i--; )
    {
        x = new int (i);
        myList->insert (x);
    }//end for ( ; i--; )

    i = 5;
    it = myList->begin ();
    myList->search (it, i);

    x = new int (rand ());
    buff = it.swap (x);

    CHECK_EQUAL(*buff, 5);
    delete (buff);
    CHECK_EQUAL(*x, *it.read ());
}//end TEST(linkedlist_f_cpp, insert_N_search_swap_and_read)
#endif //UNIT_TESTING


/*---- Forward Declarations ----*/
//This is necessary since we are packing a source file with templates into a static linked library!
#include "event_manager.hpp"
template class LinkedList<EventManager::listener_s>;

